/*
 * Copyright (c) 2011 Obix Labs Limited
 * Redistribution and use in source and binary forms, 
 * with or without modification, are permitted provided 
 * that the following conditions are met:
 * 
 * 		Redistribution of source code must retain the above 
 * 		copyright notice, this list of conditions and the 
 * 		following disclaimer.
 *
 * 		Redistribution in binary form must reproduce the 
 * 		above copyright notice, this list of conditions 
 * 		and the following disclaimer in the documentation 
 * 		and/or other materials provided with the distribution.
 * 
 * 	THIS SOFTWARE IS PROVIDED "AS IS," WITHOUT A WARRANTY OF 
 * 	ANY KIND. ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS 
 * 	AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, 
 * 	FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, 
 * 	ARE HEREBY EXCLUDED. OBIX LABS LIMITED ("Obix Labs") AND ITS 
 * 	LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE 
 * 	AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR 
 * 	ITS DERIVATIVES. IN NO EVENT WILL Obix Labs OR ITS LICENSORS BE 
 * 	LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT, 
 * 	INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE 
 * 	DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF 
 * 	LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS 
 * 	SOFTWARE, EVEN IF Obix Labs HAS BEEN ADVISED OF THE POSSIBILITY OF 
 * 	SUCH DAMAGES.
 */

package com.obixlabs.commons.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.obixlabs.commons.types.PrimitiveTypeUtilities;


/**
 * <p>
 * Encapsulates a key-value(s) association. Each instance consists of a single key associated to 
 * zero or- more values. Instances of this class are generally stored in {@link MultiValueMap} instances.
 * </p> 
 * 
 * @see MultiValueMap
 */
public class MultiValueMapEntry extends ArrayList<Object>
{		
	private static final long serialVersionUID = 5885088714342126032L;

	/**
	 * <p>
	 * The key (unique identifier) for this association. 
	 * </p>
	 */
	private Object key;

	/**
	 * <p>
	 * A composite lock used for controlling concurrent access.
	 * </p>
	 */
	private ReadWriteLock readWriteLock;
	
	/**
	 * <p>
	 * Creates a new instance with the specified key and zero associations. 
	 * </p>
	 * 
	 * @param entryKey the identifier for the new entry.
	 */
	public MultiValueMapEntry(Object entryKey)
	{
		readWriteLock = new ReentrantReadWriteLock();
		key = entryKey;
	}

	@Override
	public boolean add(Object value)
	{
		lockForWrite();
		try { 
			return super.add(value);
		} finally {
			unlockAfterWrite();
		}
	}
	
	@Override	
	public void add(int index, Object value)
	{
		lockForWrite();
		try 
		{ 
			super.add(index, value);
		} finally {
			unlockAfterWrite();
		}				
	}		
	
	@Override
	public boolean addAll(Collection<?> values)
	{
		lockForWrite();
		try { 
			return super.addAll(values);
		} finally {
			unlockAfterWrite();
		}
	}
	
	@Override
	public boolean addAll(int index, Collection<?> values)
	{
		lockForWrite();
		try { 
			return super.addAll(index, values);
		} finally {
			unlockAfterWrite();
		}
	}		
	
	@Override
	public void clear() 
	{
		lockForWrite();
		try { super.clear();} 
		finally { unlockAfterWrite(); }								
	}

	@Override
	public MultiValueMapEntry clone()
	{
		MultiValueMapEntry result = new MultiValueMapEntry(key());		
		try 
		{
			lockForRead();
			result.addAll(this);
		}
		finally { unlockAfterRead(); }		
		return result;
	}
	
	@Override
	public boolean contains(Object value)
	{
		lockForRead();
		try {
			return super.contains(value);
		} finally {
			unlockAfterRead();
		}		
	}	
		
	@Override
	public boolean containsAll(Collection<?> c)
	{
		lockForRead();
		try {
			return super.containsAll(c);
		} finally {
			unlockAfterRead();
		}		
	}
		
	@Override
	public void ensureCapacity(int minCapacity)
	{
		lockForWrite();
		try {
			super.ensureCapacity(minCapacity);
		} finally {
			unlockAfterWrite();
		}		
	}

	@Override
	public Object get(int valueIndex)
	{
		Object result;
		lockForRead();
		try
		{
			if (valueIndex<0 || valueIndex>=size())
				throw new IndexOutOfBoundsException("Specified index '"+valueIndex+"' " +
												"is not valid. Entry currently contains '"+
													(size()-1) + "' values!");
			else if (!isEmpty() && valueIndex>= 0 && valueIndex < size())
				result = super.get(valueIndex);
			else result=null;
		}
		finally { unlockAfterRead(); }
	
		return result;
	}

	@Override
	public int indexOf(Object value)
	{
		lockForRead();
		try 
		{
			return super.indexOf(value);
		}
		finally { unlockAfterRead(); }
	}

	@Override
	public boolean isEmpty() 
	{
		lockForRead();
		try { return super.isEmpty();} 
		finally { unlockAfterRead(); }								
	}

	@Override
	public int lastIndexOf(Object value)
	{
		lockForRead();
		try 
		{
			return super.lastIndexOf(value);
		}
		finally { unlockAfterRead(); }
	}

	@Override
	public Object remove(int index)
	{
		lockForWrite();
		try 
		{ 
			if (index<0 || index>=size())
				throw new 
					IndexOutOfBoundsException("Specified index '"+index+
											"' is not valid. Entry currently contains '"+
											(size()-1) + "' values!");
			else return super.remove(index);
		} finally {
			unlockAfterWrite();
		}						
	}

	@Override
	public boolean remove(Object value)
	{
		lockForWrite();
		try { return super.remove(value);} 
		finally { unlockAfterWrite(); }						
	}

	@Override
	protected void removeRange(int fromIndex, int toIndex)
	{
		lockForWrite();
		try { super.removeRange(fromIndex, toIndex);} 
		finally { unlockAfterWrite(); }						
	}
	
	@Override
	public boolean removeAll(Collection<?> c)
	{
		lockForWrite();
		try { 
			return super.removeAll(c);
		} 
		finally { unlockAfterWrite(); }						
	}

	@Override
	public boolean retainAll(Collection<?> c)
	{
		lockForWrite();
		try { 
			return super.retainAll(c);
		} 
		finally { unlockAfterWrite(); }						
	}
	
	@Override
	public String toString()
	{
		lockForRead();
		try { 
			return super.toString();
		} 
		finally { unlockAfterRead(); }						
	}
	
	@Override
	public Object set(int index, Object value)
	{
		lockForWrite();
		try 
		{ 
			if (index < 0 || index > size())
				throw new IndexOutOfBoundsException("Specified index '"+index+
										"' is not valid. Entry currently contains '"+
											(size()-1) + "' values!");
			else return super.set(index, value);
		} finally {
			unlockAfterWrite();
		}		
	}

	@Override
	public int size() 
	{
		lockForRead();
		try { return super.size();} 
		finally { unlockAfterRead(); }								
	}
	
	@Override
	public int hashCode()
	{
		lockForRead();
		try 
		{
			int result=
				SimpleHashCodeCalculator.calculateFromFields(
						key,this.toArray());			
			return result;
		} 
		finally { unlockAfterRead(); }												
	}
	
	@Override
	public List<Object> subList(int fromIndex, int toIndex)
	{
		lockForRead();
		try {		
			return super.subList(fromIndex, toIndex);
		} finally { unlockAfterRead(); }																		
	}
	
	@Override
	public boolean equals(Object candidate)
	{
		boolean result = true;
		lockForRead();
		try 
		{		
			if (!(candidate instanceof MultiValueMapEntry))
				result=false;
			
			if (result) 
			{				
				MultiValueMapEntry entry = (MultiValueMapEntry)candidate;
				if (this.size() != entry.size())
					result = false;
				else 
				{
					int size = entry.size();
					Object value, candidateValue;
					for (int i = 0; i < size && result; i++)
					{
						value = get(i);
						candidateValue = entry.get(i);
						result = value.equals(candidateValue);
					}
				}
			}
			
		} finally { unlockAfterRead(); }
		
		return result;
	}	
	
	@Override
	public void trimToSize()
	{
		lockForWrite();
		try {		
			super.trimToSize();
		} finally { unlockAfterWrite(); }
	}
	
	/**
	 * <p>
	 * Returns the entry's key i.&nbsp;e.&nbsp; its unique identifier. 
	 * </p>
	 * @return The identifier for the instance.
	 */
	public Object key() {return key;}

	/**
	 * <p>
	 * Returns a read-only {@link Collection collection} of an entry's values. 
	 * </p>
	 * @return A read-only {@link Collection collection} of an entry's values. 
	 */
	public List<Object> readOnlyValues()
	{
		lockForRead();
		try {
			return Collections.unmodifiableList(this);
		} finally {
			unlockAfterRead();
		}		
	}


	/**
	 * <p>
	 * Returns the default value of an instance. The default value is defined as the 
	 * value at index 0 in the {@link List list} of values for an instance. 
	 * </p>
	 * @return The default value for the entry if one exists, and null otherwise.
	 */
	public Object value()
	{
		Object result;
		lockForRead();
		try {
			if (!isEmpty()) result = get(0);
			else result=null;
		} finally {
			unlockAfterRead();
		}
		
		return result;
	}
	
	/**
	 * <p>
	 * Returns the textual representation of the default value for an instance. 
	 * The default value is defined as the value at index 0 in the {@link List list} of 
	 * values for an instance.  
	 * </p>
	 * @return The default value for the instance if one exists, and null otherwise.
	 * 
	 * @see #value()
	 */
	public String stringValue()
	{
		String result;
		Object entryValue = value();
		if (entryValue!=null) result = String.valueOf(entryValue);
		else result = null;
				
		return result;
	}	
	

	/**
	 * <p>
	 * Converts the textual representation of the default value for this entry 
	 * to a {@link Long}. The default value is defined as the value at index 0 
	 * in the {@link List list} of values for the entry. 
	 * </p>
	 * @return The default value as a {@link Long}, if one exists; 
	 * and null if there is no default, or if it cannot be converted to a {@link Long}.
	 * 
	 * @see #stringValue()
	 */
	public Long longValue()
	{
		Long result;
		String entryValue = stringValue();
		if (PrimitiveTypeUtilities.isValidLong(entryValue))
			result = PrimitiveTypeUtilities.toLongValue(entryValue);
		else result = null;
			
		return result;
	}	
	
	
	/**
	 * <p>
	 * Converts the textual representation of the default value for an entry 
	 * to a {@link Double}. The default value is defined as the value at index 0 in 
	 * the {@link List list} of values for an entry.
	 * </p> 
	 * 
	 * @return The default value as a {@link Double}, if one exists; 
	 * and null if there is no default, or if it cannot be converted to a {@link Double}.
	 * 
         * @see #stringValue() 
	 */
	public Double doubleValue()
	{
		Double result;
		String entryValue = stringValue();
		if (PrimitiveTypeUtilities.isValidDouble(entryValue))
			result = new Double(entryValue);
		else result = null;
			
		return result;
	}	
	
	
	/**
	 * <p>
	 * Converts the textual representation of the default value for the entry 
	 * to an {@link Integer}. The default value is defined as the value at index 0 
	 * in the {@link List list} of values for the entry.
	 * </p> 
	 * 
	 * @return The default value for the entry as a {@link Integer},  if one exists; 
	 * and null if there is no default, or if it cannot be converted to an {@link Integer}.
	 * 
	 * @see #stringValue() 
	 */
	public Integer intValue()
	{
		Integer result;
		String entryValue = stringValue();
		if (PrimitiveTypeUtilities.isValidDouble(entryValue))
			result = new Integer(entryValue);
		else result = null;
			
		return result;
	}		
		
	/**
	 * <p>
	 * Converts the textual representation of the default value for an entry 
	 * to a {@link Boolean}. The default value is defined as the value at index 0 in 
	 * the {@link List list} of values for the entry. 
	 * </p>
	 * @return The default value for the entry as a {@link Boolean},  if one exists; 
	 * and null if there is no default, or if it cannot be converted to a {@link Boolean}.
	 */
	public Boolean booleanValue()
	{
		Boolean result;
		String entryValue = stringValue();
		if (PrimitiveTypeUtilities.isValidBoolean(entryValue))
			result = new Boolean(entryValue);
		else result = null;
			
		return result;
	}		
	
	/**
	 * <p>
	 * Converts the textual representation of the value at the given index to 
	 * a {@link Long}.
	 * </p>
	 * 
	 * @param valueIndex The index of the value to be retrieved.
	 * 
	 * @return The value stored at the specified index as a {@link Long}, and a 
	 * <code>null</code> if it cannot be represented as a {@link Long}.
	 * 
	 * @throws IndexOutOfBoundsException If the specified index is out of 
	 * range (valueIndex &lt;0 || valueIndex &gt;=size()).
	 * 
         *  @see #stringValue(int)
	 */	
	public Long longValue(int valueIndex)
	{
		Long result;
		
		String valueAsString = stringValue(valueIndex);
		if (PrimitiveTypeUtilities.isValidLong(valueAsString))
			result = PrimitiveTypeUtilities.toLongValue(valueAsString);
		else result = null;			
		return result;
	}
	
	/**
	 * <p>
         * Converts the textual representation of the value at the given index to 
         * an {@link Integer}.
	 * </p>
	 * 
	 * @param valueIndex The index of the value to be retrieved.
	 * 
	 * @return The value stored at the specified index as a Integer, and a 
	 * <code>null</code> if it cannot be represented as an {@link Integer}.
	 * 
	 * @throws IndexOutOfBoundsException If the specified index is out of 
	 * range (valueIndex &lt;0 || valueIndex &gt;=size()).
	 * 
	 *  @see #stringValue(int)
	 */	
	public Integer intValue(int valueIndex)
	{
		Integer result;		
		String valueAsString = stringValue(valueIndex);
		if (PrimitiveTypeUtilities.isValidInteger(valueAsString))
			result = new Integer(valueAsString);
		else result = null;			
		return result;
	}
	
	/**
         * <p>
         * Converts the textual representation of the value at the given index to 
         * a {@link Double}.
         * </p>
	 * 
	 * @param valueIndex The index of the value to be retrieved.
	 * @return The value stored at the specified index as a {@link Double}, or a 
	 * <code>null</code> if it cannot be represented as a {@link Double}.
	 * 
	 * @throws IndexOutOfBoundsException If the specified index is out of 
	 * range (valueIndex &lt;0 || valueIndex &gt;=size()). 
	 * 
         *  @see #stringValue(int)
	 */	
	public Double doubleValue(int valueIndex)
	{
		Double result;
		
		String valueAsString = stringValue(valueIndex);
		if (PrimitiveTypeUtilities.isValidDouble(valueAsString))
			result = new Double(valueAsString);
		else result = null;			
		return result;
	}	
	
	/**
         * <p>
         * Converts the textual representation of the value at the given index to 
         * a {@link Boolean}.
         * </p>
	 * 
	 * @param valueIndex The index of the value to be retrieved.
	 * 
	 * @return The value stored at the specified index as a {@link Boolean}, 
	 * and a <code>null</code> if it cannot be represented as a {@link Boolean}.
	 * 
	 * @throws IndexOutOfBoundsException If the specified index is out of 
	 * range (valueIndex &lt;0 || valueIndex &gt;=size()).
	 * 
         *  @see #stringValue(int)
	 */		
	public Boolean booleanValue(int valueIndex)
	{
		Boolean result;		
		String valueAsString = stringValue(valueIndex);
		if (PrimitiveTypeUtilities.isValidBoolean(valueAsString))
			result = new Boolean(valueAsString);
		else result = null;			
		return result;
	}	
		
	/**
	 * <p>
	 * Returns the textual representation of the value at the specified index. 
	 * </p>
	 * 
	 * @param valueIndex The index of the value to be retrieved.
	 * @return The string representation of the value stored at the specified index.
	 * 
	 * @throws IndexOutOfBoundsException thrown if the specified index is out of range 
	 * (valueIndex &lt;0 || valueIndex &gt;=size()). 
	 */
	public String stringValue(int valueIndex)
	{
		String result;
		Object value = get(valueIndex);
		if (value!=null)
			result=String.valueOf(value);
		else result=null;
		
		return result;
	}
	

	/**
	 * <p>
	 * Returns a reference to the internal lock used for concurrent 
	 * access.
	 * </p>
	 * @return	A reference to the internal concurrency lock.
	 */
	private ReadWriteLock getReadWriteLock() { return readWriteLock; }

	/**
	 * <p>Locks the instance for reading.</p>	
	 */
	protected void lockForRead() {getReadWriteLock().readLock().lock();}
	
	/**
	 * <p>Releases the read-lock on the instance.</p> 
	 */
	protected void unlockAfterRead() {getReadWriteLock().readLock().unlock();}

	/**
	 * <p>Locks the instance for writing.</p>	
	 */	
	protected void lockForWrite() {getReadWriteLock().writeLock().lock();}

	/**
	 * <p>Releases the write-lock on the instance.</p> 
	 */	
	protected void unlockAfterWrite() {getReadWriteLock().writeLock().unlock();}	

}//end class def